#include <list>
#include <string>
#include <exception>
#include <opencv2/core/core.hpp>
#include <stdio.h>



const size_t        MAXSIZE     = 900;
const std::string   IMAGE_FLAG  = "IMAGE";

class MatToStrException: public std::exception{
public:
    MatToStrException(const char * message): message_(message){}
    ~MatToStrException() throw(){ }
    const char* what()const throw()  { return message_.c_str(); }
private:
    std::string message_;
};

class StrToMatException: public std::exception{
public:
    StrToMatException(const char * message): message_(message){}
    ~StrToMatException()throw(){ }
    const char* what()const throw(){ return message_.c_str(); }
private:
    std::string message_;
};

std::list<std::string> cvMatToStr(const cv::Mat image,const std::string type){
    const int RED   = 2;
    const int GREEN = 1;
    const int BLUE  = 0;

    char temp[5] = "";
    

    std::list<std::string> strIm;
    // First string is image header
    std::string str = "";
    str += IMAGE_FLAG + " ";
    sprintf(temp,"%d",image.cols);
    str += std::string(temp) + " "; 
    sprintf(temp,"%d",image.rows);
    str += std::string(temp) + " ";

    int cnIm  = image.channels();
    int colsIm = image.cols;

    if (type == "BGR"){
        str += "RGB";
        strIm.push_back(str);
        str = IMAGE_FLAG;
        for(int i = 0; i < image.rows; i++)
        {
                for(int j = 0; j < image.cols; j++)
                {
                    if(str.length() >= MAXSIZE){
                        strIm.push_back(str);
                        str = IMAGE_FLAG;
                    }
                    unsigned char tempData =  image.data[i*colsIm*cnIm + j*cnIm + RED];
                    str += tempData == 0 ? 1 : tempData;
                    tempData =  image.data[i*colsIm*cnIm + j*cnIm + GREEN];
                    str += tempData == 0 ? 1 : tempData;
                    tempData =  image.data[i*colsIm*cnIm + j*cnIm + BLUE];
                    str += tempData == 0 ? 1 : tempData;
                }
        }
        strIm.push_back(str);
        
    }
    else if (type == "GRAY"){
        str += "GRAY";
        strIm.push_back(str);
        str = IMAGE_FLAG;
        for(int i = 0; i < image.rows; i++)
        {
                
                for(int j = 0; j < image.cols; j++)
                {
                    if(str.size() >= MAXSIZE){
                        strIm.push_back(str);
                        str = IMAGE_FLAG;
                    }

                    unsigned char tempData =  image.data[i*colsIm*cnIm + j*cnIm];
                    str += tempData == 0 ? 1 : tempData;

                }
        }
	strIm.push_back(str);
    }
    else{
        throw MatToStrException((type + " is an invalid type!").c_str());
    }
    {
    char temp[255];
    sprintf(temp,"%d",strIm.size() - 1);
    strIm.front() += " " + std::string(temp);
    }
    return strIm;
}

cv::Mat strToCvMat(std::list<std::string> imList){
    const int RED   = 2;
    const int GREEN = 1;
    const int BLUE  = 0;

    if (imList.empty()){
        throw StrToMatException("list is empty. Empty list not allowed");
    }
    else{   
        std::string header = imList.front();
        imList.pop_front();
        
        int rows;
        int cols; 
        char type[5];
        char imHeader[6];
        cv::Mat image; 
        // Number of args == 3
        
        if(sscanf(header.c_str(),"%*s %d %d %s",&cols,&rows,type) != 3){
            std::cout << header << " " << cols << " " << rows << type;
            throw MatToStrException(("Header=" + header + 
                ": failed to read header string!").c_str());
        }        
        else if (!strcmp(type,"RGB")){
           image.create(rows,cols, CV_8UC3);
           int cnIm  = image.channels();
           int colsIm = image.cols;
           int i = 0;
           int j = 0;

           while(!imList.empty()){
                
                std::string data = imList.front();
                imList.pop_front();
                if (	data.size() > IMAGE_FLAG.size() && 
			data.substr(0,IMAGE_FLAG.size()) == IMAGE_FLAG){
                    for (int k = IMAGE_FLAG.size(); k < data.size(); k=k+3)
                    {
			if(j == image.cols){
                            j = 0;
                            i++;
                        }
                        else if(j > image.cols || i > image.rows){
                            throw StrToMatException("Data corrupted! Overflow!");
                        }
                        image.data[i*colsIm*cnIm + j*cnIm + RED]   = data[k];
                        image.data[i*colsIm*cnIm + j*cnIm + GREEN] = data[k+1];
                        image.data[i*colsIm*cnIm + j*cnIm + BLUE]  = data[k+2];
                        j++;
                    }
                }
                else{
                    throw StrToMatException((data.substr(0,IMAGE_FLAG.size())
                        +" is a bad identifier").c_str());
                }
           }           

        }
        else if(!strcmp(type,"GRAY")){
           image.create(rows,cols, CV_8UC1);
           int cnIm  = image.channels();
           int colsIm = image.cols;
           int i = 0;
           int j = 0;

           while(!imList.empty()){
                
                std::string data = imList.front();
                imList.pop_front();
                if (	data.size() > IMAGE_FLAG.size() && 
			data.substr(0,IMAGE_FLAG.size()) == IMAGE_FLAG){

                    for (int k = IMAGE_FLAG.size(); k < data.size(); k++)
                    {
                        if(j == image.cols){
                            j = 0;
                            i++;
                        }                       
                        if(i > image.rows){
                            throw StrToMatException("Data corrupted! Overflow!");
                        }
                        image.data[i*colsIm*cnIm + j*cnIm]   = data[k];
                        j++;
                    }
                }
                else{
                    throw StrToMatException((data.substr(0,IMAGE_FLAG.size())
                        +" is a bad identifier").c_str());
                }
           }           

        }
        else{
            throw StrToMatException((std::string(type)
                + " is not a valid type. Data must be corrupted").c_str());
        }
        
        return image;
    }      

}


